#include "Warper.h"

#include <cmath>
#include "Lock.h"
#include "Color.h"
#include "Interpolator.h"
#include "SimpleImage.h"

using namespace std;

Warper::Warper(const AppliedWarp& warp, const Interpolator& interpolator,
    SimpleImage* output)
    : warp_(warp),
      interpolator_(&interpolator),
      output_(output),
      total_(0),
      progress_(0),
      output_mutex_(),
      progress_mutex_() {
  output_->create(warp_.outputWidth(), warp_.outputHeight(), Color(0, 0, 0));
  total_ = warp_.outputWidth() * warp_.outputHeight();
}

// Executes warp and returns the output image.
void Warper::warp() {
  Lock output_lock(output_mutex_);
  total_ = warp_.outputWidth() * warp_.outputHeight();
  for (int i = 0; i < warp_.outputHeight(); i++) {
    for (int j = 0; j < warp_.outputWidth(); j++) {
      output_->setPixel(i, j, pixel(i, j));
      progress_++;
    }
  }
}

Color Warper::pixel(int i, int j) const {
  int w = warp_.image()->width();
  int h = warp_.image()->height();
  int output_width = warp_.outputWidth();
  int output_height = warp_.outputHeight();
  int R = warp_.outputRadius();

  // Translate and flip to image-centred Cartesian coordinates.
  double x = j - 0.5 * (output_width - 1);
  double y = -(i - 0.5 * (output_height - 1));

  // Polar coordinates.
  double r = sqrt(x * x + y * y);
  // If not extending and radius is out of range, insert fill colour.
  if (!warp_.extend() && r > R) {
    return warp_.fill();
  }
  // Calculate angle and wrap to [0, 2 PI).
  double theta = atan2(x, y);
  if (theta < 0) {
    theta += 2 * M_PI;
  }
  // Exponential warp to input image coordinates.
  double v = w / (2 * M_PI) * log(r / R * (exp(2 * M_PI * h / w) - 1) + 1);
  double u = w / (2 * M_PI) * theta;
  v = (h - 1) - v;

  return interpolator_->pixel(*warp_.image(), v, u);
}

// Returns a number in [0, 1] representing percent complete.
double Warper::progress() const {
  Lock progress_lock(progress_mutex_);
  return double(progress_) / total_;
}

// Returns true if a warp has finished.
bool Warper::finished() const {
  Lock progress_lock(progress_mutex_);
  return progress_ == total_;
}

